﻿using GameFramework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;

namespace ProjectMG.Editor.DataTableTools
{
    public sealed class DataTableGenerator
    {
        private const string DataTablePath = "Assets/GameMain/DataTables";
        private const string CSharpCodePath = "Assets/GameMain/Scripts/DataTable/DataTableCode";
        private const string CSharpCodeTemplateFileName = "Assets/GameMain/Configs/DataTableCodeTemplate.txt";
        private static readonly Regex EndWithNumberRegex = new Regex(@"\d+$");
        private static readonly Regex NameRegex = new Regex(@"^[A-Z][A-Za-z0-9_]*$");

        public static DataTableProcessor CreateDataTableProcessor(string dataTableName)
        {
            return new DataTableProcessor(Utility.Path.GetRegularPath(Path.Combine(DataTablePath, dataTableName + ".txt")), Encoding.Unicode, 2, 0, null, 1, 3, 0);
        }

        public static bool CheckRawData(DataTableProcessor dataTableProcessor, string dataTableName)
        {
            bool flag = true;
            for (int i = 0; i < dataTableProcessor.RawColumnCount; i++)
            {
                string name = dataTableProcessor.GetName(i);
                if (string.IsNullOrEmpty(name) || name == "#")
                {
                    continue;
                }

                if (name.ToLower() == "#id")
                {
                    continue;
                }

                if (!NameRegex.IsMatch(name))
                {
                    Debug.LogError(Utility.Text.Format("Check raw data failure. DataTableName='{0}' Name='{1}'", dataTableName, name));
                    flag = false;
                }
            }

            return flag;
        }

        public static void GenerateDataFile(DataTableProcessor dataTableProcessor, string dataTableName)
        {
            string binaryDataFileName = Utility.Path.GetRegularPath(Path.Combine(DataTablePath, dataTableName + ".bytes"));
            if (!dataTableProcessor.GenerateDataFile(binaryDataFileName) && File.Exists(binaryDataFileName))
            {
                File.Delete(binaryDataFileName);
            }
        }

        public static void GenerateStringFile(DataTableProcessor dataTableProcessor, string dataTableName)
        {
            SortedDictionary<string, int> stringCount = new SortedDictionary<string, int>();
            for (int i = dataTableProcessor.ContentStartRow; i < dataTableProcessor.RawRowCount; i++)
            {
                if (dataTableProcessor.IsCommentRow(i))
                {
                    continue;
                }

                for (int j = 0; j < dataTableProcessor.RawColumnCount; j++)
                {
                    if (dataTableProcessor.GetLanguageKeyword(j) != "string")
                    {
                        continue;
                    }

                    string str = dataTableProcessor.GetValue(i, j);
                    if (stringCount.ContainsKey(str))
                    {
                        stringCount[str]++;
                    }
                    else
                    {
                        stringCount[str] = 1;
                    }
                }
            }

            string stringFileName = Utility.Path.GetRegularPath(Path.Combine(DataTablePath, "String_" + dataTableName + ".txt"));
            using (FileStream fileStream = new FileStream(stringFileName, FileMode.Create))
            {
                using (StreamWriter stream = new StreamWriter(fileStream, Encoding.UTF8))
                {
                    foreach (var i in stringCount)
                    {
                        stream.Write(i.Key);
                        stream.Write('\t');
                        stream.Write(i.Value);
                        stream.Write('\r');
                        stream.Write('\n');
                    }
                }
            }
        }

        public static void GenerateCodeFile(DataTableProcessor dataTableProcessor, string dataTableName)
        {
            bool isChanged = CheckIsChanged(dataTableProcessor, dataTableName);
            if (!isChanged)
            {
                Debug.Log($"DR{dataTableName} is not Changed,don't have to regenerate it");
                return;
            }

            dataTableProcessor.SetCodeTemplate(CSharpCodeTemplateFileName, Encoding.UTF8);
            dataTableProcessor.SetCodeGenerator(DataTableCodeGenerator);

            string csharpCodeFileName = Utility.Path.GetRegularPath(Path.Combine(CSharpCodePath, "DR" + dataTableName + ".cs"));
            if (!dataTableProcessor.GenerateCodeFile(csharpCodeFileName, Encoding.UTF8, dataTableName) && File.Exists(csharpCodeFileName))
            {
                File.Delete(csharpCodeFileName);
            }
        }

        private static bool CheckIsChanged(DataTableProcessor dataTableProcessor, string dataTableName)
        {
            string oldCsharpCodePath = Path.Combine(CSharpCodeTemplateFileName, "DR" + dataTableName + ".cs");
            if (!File.Exists(oldCsharpCodePath))
            {
                return true;
            }
            var stringBuilder = new StringBuilder(File.ReadAllText(CSharpCodeTemplateFileName, Encoding.UTF8));
            DataTableCodeGenerator(dataTableProcessor, stringBuilder, dataTableName);
            string csharpCode = GetNotHeadString(stringBuilder.ToString());
            string oldCsharpCode = GetNotHeadString(File.ReadAllText(oldCsharpCodePath));
            return csharpCode != oldCsharpCode;
        }

        private static string GetNotHeadString(string str)
        {
            int index = str.IndexOf("using", StringComparison.Ordinal);
            str = str.Substring(index);
            return str;
        }

        private static void DataTableCodeGenerator(DataTableProcessor dataTableProcessor, StringBuilder codeContent, object userData)
        {
            string dataTableName = (string)userData;

            codeContent.Replace("__DATA_TABLE_CREATE_TIME__", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
            codeContent.Replace("__DATA_TABLE_NAME_SPACE__", "ProjectMG");
            codeContent.Replace("__DATA_TABLE_CLASS_NAME__", "DR" + dataTableName);
            codeContent.Replace("__DATA_TABLE_COMMENT__", dataTableProcessor.GetValue(0, 1) + "。");
            codeContent.Replace("__DATA_TABLE_ID_COMMENT__", "获取" + dataTableProcessor.GetComment(dataTableProcessor.IdColumn) + "。");
            codeContent.Replace("__DATA_TABLE_PROPERTIES__", GenerateDataTableProperties(dataTableProcessor));
            codeContent.Replace("__DATA_TABLE_PARSER__", GenerateDataTableParser(dataTableProcessor));
            codeContent.Replace("__DATA_TABLE_PROPERTY_ARRAY__", GenerateDataTablePropertyArray(dataTableProcessor));
        }

        private static string GenerateDataTableProperties(DataTableProcessor dataTableProcessor)
        {
            StringBuilder stringBuilder = new StringBuilder();
            bool firstProperty = true;
            for (int i = 0; i < dataTableProcessor.RawColumnCount; i++)
            {
                if (dataTableProcessor.IsCommentColumn(i))
                {
                    // 注释列
                    continue;
                }

                if (dataTableProcessor.IsIdColumn(i))
                {
                    // 编号列
                    continue;
                }

                if (firstProperty)
                {
                    firstProperty = false;
                }
                else
                {
                    stringBuilder.AppendLine().AppendLine();
                }

                stringBuilder
                    .AppendLine("        /// <summary>")
                    .AppendFormat("        /// 获取{0}。", dataTableProcessor.GetComment(i)).AppendLine()
                    .AppendLine("        /// </summary>")
                    .AppendFormat("        public {0} {1}", dataTableProcessor.GetLanguageKeyword(i), dataTableProcessor.GetName(i)).AppendLine()
                    .AppendLine("        {")
                    .AppendLine("            get;")
                    .AppendLine("            private set;")
                    .Append("        }");
            }

            return stringBuilder.ToString();
        }
        private static string GenerateDataTableParser(DataTableProcessor dataTableProcessor)
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder
                .AppendLine("        public override bool ParseDataRow(string dataRowString, object userData)")
                .AppendLine("        {")
                .AppendLine("            string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);")
                .AppendLine("            for (int i = 0; i < columnStrings.Length; i++)")
                .AppendLine("            {")
                .AppendLine("                columnStrings[i] = columnStrings[i].Trim(DataTableExtension.DataTrimSeparators);")
                .AppendLine("            }")
                .AppendLine()
                .AppendLine("            int index = 0;")
                .AppendLine("            int? key = null;")
                .AppendLine("            try")
                .AppendLine("            {");

            for (int i = 0; i < dataTableProcessor.RawColumnCount; i++)
            {
                if (dataTableProcessor.IsCommentColumn(i))
                {
                    // 注释列
                    stringBuilder.AppendLine("                index++;");
                    continue;
                }

                if (dataTableProcessor.IsIdColumn(i))
                {
                    // 编号列
                    stringBuilder.AppendLine("                m_Id = int.Parse(columnStrings[index++]);");
                    stringBuilder.AppendLine("                key = m_Id;");
                    continue;
                }

                if (dataTableProcessor.IsSystem(i))
                {
                    string languageKeyword = dataTableProcessor.GetLanguageKeyword(i);
                    if (languageKeyword == "string")
                    {
                        stringBuilder.AppendFormat("                {0} = columnStrings[index++];", dataTableProcessor.GetName(i)).AppendLine();
                    }
                    else
                    {
                        stringBuilder.AppendFormat("                {0} = {1}.Parse(columnStrings[index++]);", dataTableProcessor.GetName(i), languageKeyword).AppendLine();
                    }
                }
                else
                {
                    if (dataTableProcessor.IsListColumn(i))
                    {
                        Type[] t = dataTableProcessor.GetDataProcessor(i).GetType().GetGenericArguments();
                        DataTableProcessor.DataProcessor dataProcessor = Activator.CreateInstance(t[0]) as DataTableProcessor.DataProcessor;
                        string languageKeyword = dataProcessor.LanguageKeyword;
                        if (dataProcessor.IsEnum)
                        {
                            languageKeyword = DataTableProcessorExtensions.GetFullNameWithNotDot(languageKeyword);
                        }

                        stringBuilder.AppendFormat("                {0} = DataTableExtension.ParseList<{1}>(columnStrings[index++]);", dataTableProcessor.GetName(i), languageKeyword).AppendLine();
                        continue;
                    }

                    if (dataTableProcessor.IsArrayColumn(i))
                    {
                        Type[] t = dataTableProcessor.GetDataProcessor(i).GetType().GetGenericArguments();
                        DataTableProcessor.DataProcessor dataProcessor = Activator.CreateInstance(t[0]) as DataTableProcessor.DataProcessor;
                        string languageKeyword = dataProcessor.LanguageKeyword;

                        if (dataProcessor.IsEnum)
                        {
                            languageKeyword = DataTableProcessorExtensions.GetFullNameWithNotDot(languageKeyword);
                        }

                        stringBuilder.AppendFormat("                {0} = DataTableExtension.ParseArray<{1}>(columnStrings[index++]);", dataTableProcessor.GetName(i), languageKeyword).AppendLine();
                        continue;
                    }

                    if (dataTableProcessor.IsDictionaryColumn(i))
                    {
                        Type[] t = dataTableProcessor.GetDataProcessor(i).GetType().GetGenericArguments();
                        DataTableProcessor.DataProcessor dataProcessorT1 = Activator.CreateInstance(t[0]) as DataTableProcessor.DataProcessor;
                        DataTableProcessor.DataProcessor dataProcessorT2 = Activator.CreateInstance(t[1]) as DataTableProcessor.DataProcessor;
                        string languageKeyword_Key = dataProcessorT1.LanguageKeyword;
                        if (dataProcessorT1.IsEnum)
                        {
                            languageKeyword_Key = DataTableProcessorExtensions.GetFullNameWithNotDot(languageKeyword_Key);
                        }

                        string languageKeyword_Value = dataProcessorT2.LanguageKeyword;
                        if (dataProcessorT2.IsEnum)
                        {
                            languageKeyword_Value = DataTableProcessorExtensions.GetFullNameWithNotDot(languageKeyword_Value);
                        }

                        stringBuilder.AppendFormat("                {0} = DataTableExtension.ParseDictionary<{1},{2}>(columnStrings[index++]);", dataTableProcessor.GetName(i), languageKeyword_Key, languageKeyword_Value).AppendLine();
                        continue;
                    }

                    if (dataTableProcessor.IsEnumrColumn(i))
                    {
                        stringBuilder.AppendLine($"                {dataTableProcessor.GetName(i)} = UGFExtensions.DataTableExtension.EnumParse<{dataTableProcessor.GetLanguageKeyword(i)}>(columnStrings[index++]);");
                        continue;
                    }

                    stringBuilder.AppendFormat("                {0} = DataTableExtension.Parse{1}(columnStrings[index++]);", dataTableProcessor.GetName(i), dataTableProcessor.GetType(i).Name).AppendLine();
                }
            }

            stringBuilder
                .AppendLine("            }")
                .AppendLine("            catch (Exception e)")
                .AppendLine("            {")
                .AppendLine("                ((DataTableHelper.ParseDataRowError)userData).Set(index, key, e);")
                .AppendLine("                return false;")
                .AppendLine("            }")
                .AppendLine()
                .AppendLine("            return true;")
                .AppendLine("        }")
                .AppendLine()
                .AppendLine("        public override bool ParseDataRow(byte[] dataRowBytes, int startIndex, int length, object userData)")
                .AppendLine("        {")
                .AppendLine("            Tuple<BinaryReader, GameFrameworkFunc<int, string>> tuple = (Tuple<BinaryReader, GameFrameworkFunc<int, string>>)userData;")
                .AppendLine("            BinaryReader binaryReader = tuple.Item1;")
                .AppendLine("            GameFrameworkFunc<int, string> GetString = tuple.Item2;")
                .AppendLine();

            for (int i = 0; i < dataTableProcessor.RawColumnCount; i++)
            {
                if (dataTableProcessor.IsCommentColumn(i))
                {
                    // 注释列
                    continue;
                }

                if (dataTableProcessor.IsIdColumn(i))
                {
                    // 编号列
                    stringBuilder.AppendLine("            m_Id = binaryReader.Read7BitEncodedInt32();");
                    continue;
                }

                if (dataTableProcessor.IsListColumn(i))
                {
                    Type[] t = dataTableProcessor.GetDataProcessor(i).GetType().GetGenericArguments();
                    DataTableProcessor.DataProcessor dataProcessor = Activator.CreateInstance(t[0]) as DataTableProcessor.DataProcessor;
                    string _languageKeyword = dataProcessor.LanguageKeyword;
                    if (dataProcessor.IsEnum)
                    {
                        _languageKeyword = DataTableProcessorExtensions.GetFullNameWithNotDot(_languageKeyword);
                    }

                    stringBuilder.AppendFormat("            {0} = binaryReader.ParseList<{1}>();", dataTableProcessor.GetName(i), _languageKeyword).AppendLine();
                    continue;
                }

                if (dataTableProcessor.IsArrayColumn(i))
                {
                    Type[] t = dataTableProcessor.GetDataProcessor(i).GetType().GetGenericArguments();
                    DataTableProcessor.DataProcessor dataProcessor = Activator.CreateInstance(t[0]) as DataTableProcessor.DataProcessor;
                    string _languageKeyword = dataProcessor.LanguageKeyword;

                    if (dataProcessor.IsEnum)
                    {
                        _languageKeyword = DataTableProcessorExtensions.GetFullNameWithNotDot(_languageKeyword);
                    }

                    stringBuilder.AppendFormat("            {0} = binaryReader.ParseArray<{1}>();", dataTableProcessor.GetName(i), _languageKeyword).AppendLine();
                    continue;
                }

                if (dataTableProcessor.IsDictionaryColumn(i))
                {
                    Type[] t = dataTableProcessor.GetDataProcessor(i).GetType().GetGenericArguments();
                    DataTableProcessor.DataProcessor dataProcessorT1 = Activator.CreateInstance(t[0]) as DataTableProcessor.DataProcessor;
                    DataTableProcessor.DataProcessor dataProcessorT2 = Activator.CreateInstance(t[1]) as DataTableProcessor.DataProcessor;
                    string _languageKeyword_Key = dataProcessorT1.LanguageKeyword;
                    if (dataProcessorT1.IsEnum)
                    {
                        _languageKeyword_Key = DataTableProcessorExtensions.GetFullNameWithNotDot(_languageKeyword_Key);
                    }

                    var _languageKeyword_Value = dataProcessorT2.LanguageKeyword;
                    if (dataProcessorT2.IsEnum)
                    {
                        _languageKeyword_Value = DataTableProcessorExtensions.GetFullNameWithNotDot(_languageKeyword_Value);
                    }

                    stringBuilder.AppendFormat("            {0} = binaryReader.ParseDictionary<{1},{2}>();", dataTableProcessor.GetName(i), _languageKeyword_Key, _languageKeyword_Value).AppendLine();
                    continue;
                }

                if (dataTableProcessor.IsEnumrColumn(i))
                {
                    stringBuilder.AppendFormat("            {0} = ({1})binaryReader.Read7BitEncodedInt32();", dataTableProcessor.GetName(i), dataTableProcessor.GetLanguageKeyword(i)).AppendLine();
                    continue;
                }

                string languageKeyword = dataTableProcessor.GetLanguageKeyword(i);
                stringBuilder.AppendFormat("            {0} = binaryReader.ParseSystemType<{1}>();", dataTableProcessor.GetName(i), languageKeyword).AppendLine();
            }

            stringBuilder
                .AppendLine()
                .AppendLine("            return true;")
                .Append("        }");

            return stringBuilder.ToString();
        }

        private static string GenerateDataTablePropertyArray(DataTableProcessor dataTableProcessor)
        {
            List<PropertyCollection> propertyCollections = new List<PropertyCollection>();
            for (int i = 0; i < dataTableProcessor.RawColumnCount; i++)
            {
                if (dataTableProcessor.IsCommentColumn(i))
                {
                    // 注释列
                    continue;
                }

                if (dataTableProcessor.IsIdColumn(i))
                {
                    // 编号列
                    continue;
                }

                string name = dataTableProcessor.GetName(i);
                if (!EndWithNumberRegex.IsMatch(name))
                {
                    continue;
                }

                string propertyCollectionName = EndWithNumberRegex.Replace(name, string.Empty);
                int id = int.Parse(EndWithNumberRegex.Match(name).Value);

                PropertyCollection propertyCollection = null;
                foreach (PropertyCollection pc in propertyCollections)
                {
                    if (pc.Name == propertyCollectionName)
                    {
                        propertyCollection = pc;
                        break;
                    }
                }

                if (propertyCollection == null)
                {
                    propertyCollection = new PropertyCollection(propertyCollectionName, dataTableProcessor.GetLanguageKeyword(i));
                    propertyCollections.Add(propertyCollection);
                }

                propertyCollection.AddItem(id, name);
            }

            if (propertyCollections.Count <= 0)
            {
                return "        // No Property Collection";
            }

            StringBuilder stringBuilder = new StringBuilder();
            bool firstProperty = true;
            foreach (PropertyCollection propertyCollection in propertyCollections)
            {
                if (firstProperty)
                {
                    firstProperty = false;
                }
                else
                {
                    stringBuilder.AppendLine().AppendLine();
                }

                stringBuilder
                    .AppendFormat("        public int {0}Count", propertyCollection.Name).AppendLine()
                    .AppendLine("        {")
                    .AppendLine("            get")
                    .AppendLine("            {")
                    .AppendFormat("                return {0};", propertyCollection.ItemCount).AppendLine()
                    .AppendLine("            }")
                    .AppendLine("        }")
                    .AppendLine()
                    .AppendFormat("        public {1} Get{0}(int id)", propertyCollection.Name, propertyCollection.LanguageKeyword).AppendLine()
                    .AppendLine("        {")
                    .AppendLine("            switch (id)")
                    .AppendLine("            {");

                int itemCount = propertyCollection.ItemCount;
                for (int i = 0; i < itemCount; i++)
                {
                    KeyValuePair<int, string> item = propertyCollection.GetItem(i);
                    stringBuilder
                        .AppendFormat("                case {0}:", item.Key).AppendLine()
                        .AppendFormat("                    return {0};", item.Value).AppendLine().AppendLine();
                }

                stringBuilder
                    .AppendLine("                default:")
                    .AppendFormat("                    throw new GameLogicException(Utility.Text.Format(\"Get{0} with invalid id '{{0}}'.\", id));", propertyCollection.Name).AppendLine()
                    .AppendLine("            }")
                    .AppendLine("        }")
                    .AppendLine()
                    .AppendFormat("        public {1} Get{0}At(int index)", propertyCollection.Name, propertyCollection.LanguageKeyword).AppendLine()
                    .AppendLine("        {")
                    .AppendLine("            switch (index)")
                    .AppendLine("            {");

                for (int i = 0; i < itemCount; i++)
                {
                    KeyValuePair<int, string> item = propertyCollection.GetItem(i);
                    stringBuilder
                        .AppendFormat("                case {0}:", i).AppendLine()
                        .AppendFormat("                    return {0};", item.Value).AppendLine().AppendLine();
                }

                stringBuilder
                    .AppendLine("                default:")
                    .AppendFormat("                    throw new GameLogicException(Utility.Text.Format(\"Get{0}At with invalid index '{{0}}'.\", index));", propertyCollection.Name).AppendLine()
                    .AppendLine("            }")
                    .Append("        }");
            }

            return stringBuilder.ToString();
        }

        private sealed class PropertyCollection
        {
            private readonly string m_Name;
            private readonly string m_LanguageKeyword;
            private readonly List<KeyValuePair<int, string>> m_Items;

            public PropertyCollection(string name, string languageKeyword)
            {
                m_Name = name;
                m_LanguageKeyword = languageKeyword;
                m_Items = new List<KeyValuePair<int, string>>();
            }

            public string Name
            {
                get
                {
                    return m_Name;
                }
            }

            public string LanguageKeyword
            {
                get
                {
                    return m_LanguageKeyword;
                }
            }

            public int ItemCount
            {
                get
                {
                    return m_Items.Count;
                }
            }

            public KeyValuePair<int, string> GetItem(int index)
            {
                if (index < 0 || index >= m_Items.Count)
                {
                    throw new GameFrameworkException(Utility.Text.Format("GetItem with invalid index '{0}'.", index));
                }

                return m_Items[index];
            }

            public void AddItem(int id, string propertyName)
            {
                m_Items.Add(new KeyValuePair<int, string>(id, propertyName));
            }
        }
    }
}
